﻿using System;
using System.Linq.Expressions;
using MoroMoro.Samples.ToDoList.Base.Domain.ExpressionVisitors;
using Expression_ = System.Linq.Expressions.Expression;

namespace MoroMoro.Samples.ToDoList.Base.Domain.Specifications
{
    /// <summary>
    /// 二つの仕様を組み合わせて表現される仕様を表します。
    /// </summary>
    /// <typeparam name="TEntity">エンティティの型。</typeparam>
    /// <seealso cref="Specifications.Specification&lt;TEntity&gt;"/>
    [Serializable]
    internal abstract class BinarySpecification<TEntity> : Specification<TEntity>
        where TEntity : class
    {
        /// <summary>
        /// <see cref="BinarySpecification&lt;TEntity&gt;"/> クラスの新しいインスタンスを初期化します。
        /// </summary>
        /// <param name="left">一つ目の仕様。</param>
        /// <param name="right">二つ目の仕様。</param>
        /// <exception cref="System.ArgumentNullException">left または right が null です。</exception>
        public BinarySpecification(Specification<TEntity> left, Specification<TEntity> right)
        {
            if (left == null)
            {
                throw new ArgumentNullException("left");
            }
            if (right == null)
            {
                throw new ArgumentNullException("right");
            }
            _left = left;
            _right = right;
        }


        /// <summary>
        /// 一つ目の仕様。
        /// </summary>
        private readonly Specification<TEntity> _left;

        /// <summary>
        /// 二つ目の仕様。
        /// </summary>
        private readonly Specification<TEntity> _right;


        /// <inheritdoc/>
        protected sealed override Expression<Func<TEntity, bool>> CreateExpression()
        {
            var parameter = Expression_.Parameter(typeof(TEntity), "entity");
            var replacer = new ParameterVisitor(node => parameter);
            var leftBody = replacer.Visit(_left.Expression.Body);
            var rightBody = replacer.Visit(_right.Expression.Body);
            var newBody = CreateBody(leftBody, rightBody);
            return Expression_.Lambda<Func<TEntity, bool>>(newBody, parameter);
        }

        /// <summary>
        /// 指定した二つの仕様の式を用いる二項演算子の式を生成します。
        /// </summary>
        /// <param name="left">一つ目の仕様の式。</param>
        /// <param name="right">二つ目の仕様の式。</param>
        /// <returns>指定した二つの仕様の式を用いる二項演算子の式。</returns>
        protected abstract BinaryExpression CreateBody(Expression left, Expression right);
    }
}
